package com.vimviv;

import java.util.Vector;

import net.rim.device.api.system.Bitmap;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.FieldChangeListener;
import net.rim.device.api.ui.component.ButtonField;
import net.rim.device.api.ui.component.EditField;
import net.rim.device.api.ui.component.LabelField;
import net.rim.device.api.ui.component.ObjectChoiceField;
import net.rim.device.api.ui.container.HorizontalFieldManager;
import net.rim.device.api.ui.container.MainScreen;
import net.rim.device.api.ui.container.VerticalFieldManager;

/**
 * Java Neural Network Example Handwriting Recognition 
 * Copyright 2005 by Heaton Research, Inc. 
 * by Jeff Heaton (http://www.heatonresearch.com) 10-2005
 * ------------------------------------------------- 
 * This source code is copyrighted.
 * You may reuse this code in your own compiled projects. 
 * However, if you would like to redistribute this source code
 * in any form, you must obtain permission from Heaton Research. 
 * (support@heatonresearch.com). 
 * ------------------------------------------------- 
 * 
 * This class implements an applet that allows the user to test out
 * basic character recognition functions.
 * 
 * ------------------------------------------------- 
 * Want to learn more about Neural Network Programming in Java?
 * Have a look at our e-book:
 * 
 * http://www.heatonresearch.com/articles/series/1/
 *  
 * @author Jeff Heaton (http://www.jeffheaton.com)
 * @version 1.0
 */

public class OCRScreen extends MainScreen
{
	/**
	 * The downsample width for the application.
	 */
	static final int DOWNSAMPLE_WIDTH = 5;

	/**
	 * The down sample height for the application.
	 */
	static final int DOWNSAMPLE_HEIGHT = 7;

	Vector sampleList = new Vector();;

	/**
	 * The entry component for the user to draw into.
	 */
	Entry entry;

	/**
	 * The down sample component to display the drawing downsampled.
	 */
	Sample sample;

	/**
	 * The neural network.
	 */
	KohonenNetwork net;
	/**
	 * The add button.
	 */
	ButtonField add;

	/**
	 * The clear button
	 */
	ButtonField clear;

	/**
	 * The recognize button
	 */
	ButtonField recognize;

	ObjectChoiceField scrollPane1 = new ObjectChoiceField();

	/**
	 * The letters list box
	 */
	Vector letters = new Vector();

	/**
	 * The delete button
	 */
	ButtonField del;

	/**
	 * The delete button
	 */
	ButtonField delAll;

	/**
	 * The train button
	 */
	ButtonField train;

	LabelField message = new LabelField();

	EditField letterToAdd = new EditField();


	public static final String HANDWRITING[] = {
		"A:00110001100111001010111111100110001",
		"B:11111100011000111111100111000110111",
		"C:11111100001000010000100001100001111",
		"D:11111100011000110000100011000111111",
		"E:11111100001000011111100001000011111",
		"F:11111100001000011110100001000010000",
		"G:01110110001000010111100011000111111",
		"H:10001100001000111001111111000110001",
		"I:11111001000010000100001000010000111",
		"J:11111001000010000100101001010011100",
		"K:10001100111111011010100101001110011",
		"L:10000100001000010000100001000011111",
		"M:11111101111011110100101001010110101",
		"N:11111110111000110001100001000110001",
		"O:11111100011000110000100011000111111",
		"P:11111100011001111110100001000010000",
		"Q:01111110011000110001100111101101111",
		"R:11111100011000111011111101001110001",
		"S:01111110001100001111000010000111111",
		"T:11111001000010000100001000010000100",
		"U:10001100001000110001100011001111110",
		"V:10001100011101101011011100111000110",
		"W:10101101011010110101101011011111111",
		"X:10011110100111001100111001011010010",
		"Y:10001110110111001100010000100001000",
		"Z:11111000110011001100110001000011111", };

	/**
	 * Setup the GUI
	 */
	public OCRScreen()
	{

		preload();
		VerticalFieldManager topPanel = new VerticalFieldManager();
		VerticalFieldManager bottomPanel = new VerticalFieldManager();

		// create top button panel
		HorizontalFieldManager topButtonPanel = new HorizontalFieldManager();
		HorizontalFieldManager bottomButtonPanel = new HorizontalFieldManager();
		recognize = new ButtonField("Recognize", ButtonField.CONSUME_CLICK);
		topButtonPanel.add(recognize);
		clear = new ButtonField("Clear", ButtonField.CONSUME_CLICK);
		topButtonPanel.add(clear);
		VerticalFieldManager addPanel = new VerticalFieldManager();
		add = new ButtonField("Add:", ButtonField.CONSUME_CLICK);
		addPanel.add(add);
		addPanel.add(letterToAdd);
		topButtonPanel.add(addPanel);
		letterToAdd.setText("a");

		// create the bottom button pannel
		del = new ButtonField("Delete", ButtonField.CONSUME_CLICK);
		bottomButtonPanel.add(del);
		delAll = new ButtonField("Delete All", ButtonField.CONSUME_CLICK);
		bottomButtonPanel.add(delAll);
		train = new ButtonField("Train", ButtonField.CONSUME_CLICK);
		bottomButtonPanel.add(train);
		Bitmap bitmap = Bitmap.getBitmapResource("Background.png");
		// create top panel
		entry = new Entry(bitmap);
		message = new LabelField("Draw a capital letter, click Recognize");
		topPanel.add(message);
		topPanel.add(entry);
		topPanel.add(topButtonPanel);

		// create bottom panel
		bottomPanel.add(new LabelField("Known Letter Database"));
		VerticalFieldManager bottomContent = new VerticalFieldManager(VERTICAL_SCROLLBAR);
		bottomPanel.add(bottomContent);
		bottomPanel.add(bottomButtonPanel);

		// create the letters panel
		VerticalFieldManager lettersPanel = new VerticalFieldManager();
		String[] lArray = new String[letters.size()];
		letters.copyInto(lArray);
		scrollPane1.setChoices(lArray);
		updateList();
		lettersPanel.add(scrollPane1);

		// create the downsample panel
		VerticalFieldManager downSamplePanel = new VerticalFieldManager();
		sample = new Sample(DOWNSAMPLE_WIDTH, DOWNSAMPLE_HEIGHT);
		entry.setSample(sample);
		downSamplePanel.add(sample);

		bottomContent.add(lettersPanel);
		bottomContent.add(downSamplePanel);

		add(topPanel);
		add(bottomPanel);

		SymAction lSymAction = new SymAction();
		clear.setChangeListener(lSymAction);
		add.setChangeListener(lSymAction);
		del.setChangeListener(lSymAction);
		delAll.setChangeListener(lSymAction);
		SymListSelection lSymListSelection = new SymListSelection();
		scrollPane1.setChangeListener(lSymListSelection);

		train.setChangeListener(lSymAction);
		recognize.setChangeListener(lSymAction);


		entry.setFocus();

	}

	
	class SymAction implements FieldChangeListener
	{

		public void fieldChanged(Field field, int context) {
			if (field == clear)
				clear_actionPerformed();
			else if (field == add)
				add_actionPerformed();
			else if (field == del)
				del_actionPerformed();
			else if (field == delAll)
				deleteAll_actionPerformed();
			else if (field == train)
				train_actionPerformed();
			else if (field == recognize)
				recognize_actionPerformed();

		}
	}

	/**
	 * Called to clear the image.
	 * 
	 * @param event
	 *          The event
	 */
	void clear_actionPerformed()
	{
		entry.clear();
		sample.getData().clear();
		sample.getManager().invalidate();
	}

	/**
	 * Called to clear the image.
	 * 
	 * @param event
	 *          The event
	 */
	void deleteAll_actionPerformed()
	{
		sampleList.removeAllElements();
		net = null;
		updateList();
		entry.clear();
		sample.getData().clear();
		sample.getManager().invalidate();
	}

	/**
	 * Called to add the current image to the training set
	 * 
	 * @param event
	 *          The event
	 */
	void add_actionPerformed()
	{
		int i;

		String letter = letterToAdd.getText().trim();

		if (letter.length() > 1)
		{
			message.setText("Enter only one letter.");
			return;
		}

		if (letter.length() < 1)
		{
			message.setText("Enter a letter to add.");
			return;
		}

		entry.downSample();
		SampleData sampleData = (SampleData) sample.getData().clone();
		sampleData.setLetter(letter.charAt(0));

		for (i = 0; i < sampleList.size(); i++)
		{
			SampleData str = (SampleData) sampleList.elementAt(i);
			if (str.equals(sampleData))
			{
				message.setText("Letter already defined, delete it first!");
				return;
			}

			if (str.compareTo(sampleData) > 0)
			{
				sampleList.insertElementAt(sampleData, i);
				updateList();
				return;
			}
		}
		sampleList.insertElementAt(sampleData, sampleList.size());
		updateList();
		scrollPane1.setSelectedIndex(i);
		entry.clear();
		sample.getManager().invalidate();

	}

	/**
	 * Called when the del button is pressed.
	 * 
	 * @param event
	 *          The event.
	 */
	void del_actionPerformed()
	{
		int i = scrollPane1.getSelectedIndex();

		if (i == -1)
		{
			message.setText("Please select a letter to delete.");

			return;
		}

		sampleList.removeElementAt(i);
		updateList();
	}

	class SymListSelection implements FieldChangeListener
	{

		public void fieldChanged(Field field, int context) {
			if (field == scrollPane1)
				letters_valueChanged();

		}
	}

	/**
	 * Called when a letter is selected from the list box.
	 * 
	 * @param event
	 *          The event
	 */
	void letters_valueChanged()
	{
		if (scrollPane1.getSelectedIndex() == -1)
			return;
		SampleData selected = (SampleData) sampleList.elementAt(scrollPane1
				.getSelectedIndex());
		sample.setData((SampleData) selected.clone());
		sample.getManager().invalidate();
		entry.clear();
	}

	/**
	 * Called when the train button is pressed.
	 * 
	 * @param event
	 *          The event.
	 */
	void train_actionPerformed()
	{

		try
		{
			int inputNeuron = OCRScreen.DOWNSAMPLE_HEIGHT
			* OCRScreen.DOWNSAMPLE_WIDTH;
			int outputNeuron = sampleList.size();

			TrainingSet set = new TrainingSet(inputNeuron, outputNeuron);
			set.setTrainingSetCount(sampleList.size());

			for (int t = 0; t < sampleList.size(); t++)
			{
				int idx = 0;
				SampleData ds = (SampleData) sampleList.elementAt(t);
				for (int y = 0; y < ds.getHeight(); y++)
				{
					for (int x = 0; x < ds.getWidth(); x++)
					{
						set.setInput(t, idx++, ds.getData(x, y) ? .5 : -.5);
					}
				}
			}

			net = new KohonenNetwork(inputNeuron, outputNeuron, this);
			net.setTrainingSet(set);
			net.learn();
			this.clear_actionPerformed();
			message.setText("Trained. Ready to recognize.");
		} catch (Exception e)
		{
			message.setText("Exception:" + e.getMessage());

		}

	}

	/**
	 * Called when the recognize button is pressed.
	 * 
	 * @param event
	 *          The event.
	 */
	void recognize_actionPerformed()
	{
		if (net == null)
		{
			message.setText("I need to be trained first!");
			return;
		}
		entry.downSample();

		double input[] = new double[5 * 7];
		int idx = 0;
		SampleData ds = sample.getData();
		for (int y = 0; y < ds.getHeight(); y++)
		{
			for (int x = 0; x < ds.getWidth(); x++)
			{
				input[idx++] = ds.getData(x, y) ? .5 : -.5;
			}
		}

		double normfac[] = new double[1];
		double synth[] = new double[1];

		int best = net.winner(input, normfac, synth);
		char map[] = mapNeurons();
		message.setText("  " + map[best] + "   (Neuron #" + best + " fired)");
		clear_actionPerformed();

	}

	/**
	 * Used to map neurons to actual letters.
	 * 
	 * @return The current mapping between neurons and letters as an array.
	 */
	char[] mapNeurons()
	{

		char map[] = new char[sampleList.size()];
		double normfac[] = new double[1];
		double synth[] = new double[1];

		for (int i = 0; i < map.length; i++)
			map[i] = '?';
		for (int i = 0; i < sampleList.size(); i++)
		{
			double input[] = new double[5 * 7];
			int idx = 0;
			SampleData ds = (SampleData) sampleList.elementAt(i);
			for (int y = 0; y < ds.getHeight(); y++)
			{
				for (int x = 0; x < ds.getWidth(); x++)
				{
					input[idx++] = ds.getData(x, y) ? .5 : -.5;
				}
			}

			int best = net.winner(input, normfac, synth);
			map[best] = ds.getLetter();
		}
		return map;
	}

	public void updateList()
	{
//		String[] empty = {"*Empty*"};
		String[] sampleArray = new String[sampleList.size()];
//		scrollPane1.setChoices(empty);
		for (int i = 0; i < sampleList.size(); i++)
		{
			SampleData sample = (SampleData) sampleList.elementAt(i);
			sampleArray[i] = "" + sample.letter;
		}
		scrollPane1.setChoices(sampleArray);
	}

	public void preload()
	{
		int index = 0;
		for (int i = 0; i < OCRScreen.HANDWRITING.length; i++)
		{
			String line = HANDWRITING[i].trim();
			SampleData ds = new SampleData(line.charAt(0),
					OCRScreen.DOWNSAMPLE_WIDTH, OCRScreen.DOWNSAMPLE_HEIGHT);
			sampleList.insertElementAt(ds, index++);
			int idx = 2;
			for (int y = 0; y < ds.getHeight(); y++)
			{
				for (int x = 0; x < ds.getWidth(); x++)
				{
					ds.setData(x, y, line.charAt(idx++) == '1');
				}
			}
		}
		
		train_actionPerformed();
	}

}
